/*
 * Copyright 2012 Niklas Rehfeld
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.madpie.transport;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Scanner;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.prefs.Preferences;

/**
 * Executes commands in a terminal. This class is used to get around the fact
 * that
 * <code> ssh </code> and friends don't allow you to pass passwords to them
 * directly, either via stdin or on the command line (they check for a proper
 * TTY). Therefore we have to run them in a terminal, and make the user enter
 * the password in that.
 * <p/>
 * This has the added benefit that we can do shell redirects in the terminal, so
 * the output from the commands run over ssh can be piped to other commands or
 * sent directly to a file. 
 *
 * @author Niklas Rehfeld
 */
public class SshTermWrapper
{

    protected static Preferences prefs;
    private String host, user;

    /**
     * Create a new instance.
     *
     * @param user the username to log into the remote host as
     * @param host the remote host to log into
     */
    public SshTermWrapper(String user, String host)
    {
        this.host = host;
        this.user = user;
        prefs = Preferences.userNodeForPackage(SshTermWrapper.class);
    }

    /**
     * Runs a command in a terminal.
     *
     * //BUG process always returns 0 even if command has failed. 
     * 
     * @param command the command to run.
     *
     * @return the process that controls the command.
     */
    public Process runInTerminal(String command)
    {
        //load terminal emulator from prefs, defaults to xterm.
        ProcessBuilder term = new ProcessBuilder(prefs.get("terminal", "xterm"));
        term.command().add("-e");
        term.command().add(command);
        try
        {
            return term.start();
        } catch (IOException ex)
        {
            Logger.getLogger(SshTermWrapper.class.getName()).log(Level.SEVERE,
                    null, ex);
        }
        return null;

    }

    /**
     * Runs a command on a remote server over ssh.
     * <p/>
     * Note that any redirects should be given as part of the command, and are
     * <emph>local</emph>, i.e.
     * <code>runSshInTerminal("ls >> output")</code> will result in a local file
     * <code>output</code> being created on the local machine with the results
     * of running
     * <code>ls</code> on the remote machine. If the idea is to redirect
     * something on the server machine, there need to be extra quotes. e.g.
     * <code> runSshInTerminal(" 'ls | tee serv.out' >> output")</code> will
     * also pipe the output of ls to the tee command on the server machine.
     * <p/>
     * Note also that the returned process is the process for the terminal, not
     * the command being run. Therefore the input and output streams are fairly
     * useless. The only thing you can really do with the returned process is
     * kill it if it hangs, and maybe check the return value.
     * <p/>
     * Warning, this works with
     * <code>xterm</code>, but may or may not work with other terminal emulators
     * (e.g.
     * <code>konsole, lxterm, gnome-terminal</code> etc.)
     *
     * @param command the command to run on the remote server.
     *
     * @return the process that controls the terminal.
     */
    public Process runSshInTerminal(String command)
    {
        if (user == null || host == null)
        {
            throw new IllegalStateException("No user or no host supplied");
        }
        return runInTerminal("ssh " + user + "@" + host + " " + command);
    }

    /**
     * Tests if a
     * <code>ssh</code> connection works. This is done by sending the
     * command<br/>
     * <code> ssh $user@$host 'echo hello' > /tmp/madpie.testout</code> <br/>
     * and checking if the file <code>/tmp/madpie.testout</code> consists of the
     * string "hello".
     *
     * @return true if the connection seems to be working.
     */
    public boolean testSshConnection()
    {
        if (user == null || host == null)
        {
            return false;
        }
        Process p = runSshInTerminal("'echo hello' > /tmp/madpie.testout");
        try
        {
            p.waitFor();
        } catch (InterruptedException ex)
        {
            Logger.getLogger(SshTermWrapper.class.getName()).log(Level.SEVERE,
                    null,
                    ex);
            return false;
        }
        File f = new File("/tmp/madpie.testout");
        try
        {
            Scanner s = new Scanner(f);
            String str = "";
            if (s.hasNextLine())
            {
                str = s.nextLine().trim();
                return str.equals("hello");
            }
            Logger.getLogger(SshTermWrapper.class.getName()).
                    log(Level.WARNING,
                    "Connection failed. Expected ''hello'', got '' {0} '' ", str);
            return false;
        } catch (FileNotFoundException ex)
        {
            Logger.getLogger(SshTermWrapper.class.getName()).log(Level.SEVERE,
                    null,
                    ex);
            return false;
        } finally
        {
            if (f.exists())
            {
                f.delete();
            }
        }

    }
}
